<?php

/**
 * 无限层级支持模型
 * 
 * 基本表结构
 * id
 * parent_id
 * depth
 * parents
 * parents_reverse
 * state
 * sort
 * title
 */
class TreeModel extends \Model {

    protected $_parent_id = 'parent_id';

    protected $_parents = 'parents';

    protected $_parents_reverse = 'parents_reverse';

    protected $_depth = 'depth';

    protected $_max_depth = 500;

    protected $_state = 'state'; // 可选字段

    protected $_title = 'title'; // 可选字段

    public function add($data) {
        if ($data[$this->_parent_id]) {
            $where = array($this->_id => $data[$this->_parent_id]);
            $parent = Db()->getRow(static::$table)->where($where)->result();
            $data[$this->_depth] = $parent[$this->_depth] + 1;

            $parents = $parent[$this->_parents];
            $parents_reverse = $parent[$this->_parents_reverse];

            if ($parent[$this->_depth] > $this->_max_depth) {
                $parents = substr($parents, strpos($parents, ',') + 1);
                $parents_reverse = substr($parents_reverse, 0, strrpos($parents_reverse, ',', strlen($parents_reverse) - 2) + 1);
            }

            $data[$this->_parents] = $parents . $parent[$this->_id] . ',';
            $data[$this->_parents_reverse] = $parent[$this->_id] . ',' . $parents_reverse;
        }

        return parent::add($data);
    }

    public function update($id, $data) {
        if (isset($data[$this->_parent_id])) {
            $result = $this->setParent($id, $data[$this->_parent_id]);
            if (!$result) {
                return false;
            }
        }
        return parent::update($id, $data);
    }

    public function updateByUid($uid, $data) {
        $id = $this->getByUid($uid, $this->_id);
        if (!$id) {
            return false;
        }
        return $this->update($id, $data);
    }

    public function updateInUid($id, $uid, $data) {
        $id = $this->getInUid($id, $uid, $this->_id);
        if (!$id) {
            return false;
        }
        return $this->update($id, $data);
    }

    public function remove($id) {
        $where = array($this->_parent_id => $id);
        $childs = Db()->getCol(static::$table)->fields($this->_id)->where($where)->result();
        if ($childs) {
            foreach ($childs as $child) {
                $this->remove($child);
            }
        }
        return parent::remove($id);
    }

    public function removeByUid($uid) {
        $id = $this->getByUid($uid, $this->_id);
        if (!$id) {
            return false;
        }
        return $this->remove($id);
    }

    public function removeInUid($id, $uid) {
        $id = $this->getInUid($id, $uid, $this->_id);
        if (!$id) {
            return false;
        }
        return $this->remove($id);
    }

    public function removeChilds($id) {
        $where = array($this->_parent_id => $id);
        $childs = Db()->getCol(static::$table)->fields($this->_id)->where($where)->result();
        if ($childs) {
            foreach ($childs as $child) {
                $result = $this->remove($child);
                if (!$result) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 变更上级关系
     * @param int $id
     * @param int $parent_id
     * @return bool
     */
    public function setParent($id, $parent_id) {

        if ($id == $parent_id) {
            return false;
        }

        $data[$this->_parent_id] = $parent_id;

        $where = array($this->_id => $parent_id);
        $parent = Db()->getRow(static::$table)->where($where)->result();

        if ($parent) {
            $data[$this->_depth] = $parent[$this->_depth] + 1;
            $parents = $parent[$this->_parents];
            $parents_reverse = $parent[$this->_parents_reverse];
            if ($parent[$this->_depth] > $this->_max_depth) {
                $parents = substr($parents, strpos($parents, ',') + 1);
                $parents_reverse = substr($parents_reverse, 0, strrpos($parents_reverse, ',', strlen($parents_reverse) - 2) + 1);
            }

            $data[$this->_parents] = $parents . $parent[$this->_id] . ',';
            $data[$this->_parents_reverse] = $parent[$this->_id] . ',' . $parents_reverse;
        } else {
            $data[$this->_depth] = 0;
            $data[$this->_parents] = '';
            $data[$this->_parents_reverse] = '';
        }


        $result = parent::update($id, $data);

        if (!$result) {
            return false;
        }

        $childs = $this->getChildNodesIds($id);
        if ($childs) {
            foreach ($childs as $child) {
                $result = $this->setParent($child, $id);
                if (!$result) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 获取树
     * 该方法必须在数据表中有title字段，state字段可选
     * @return Treenode
     */
    public function getTree($state = null) {
        $where = array();
        if ($state !== null) {
            $where[$this->_state] = $state;
        }

        $rows = Db()->getAll(static::$table)->where($where)->order("{$this->_depth}, {$this->_parent_id}, {$this->_sort}, {$this->_id}")->result();

        if (!$rows) {
            return null;
        }

        $tree = new Treenode();

        foreach ($rows as $row) {
            if ($parent = $tree->getNodeByKey($row[$this->_parent_id])) {
                $parent->addChildNode(new Treenode($row[$this->_id], $row[$this->_title], $row));
            } else {
                $tree->addChildNode(new Treenode($row[$this->_id], $row[$this->_title], $row));
            }
        }

        return $tree;
    }

    /**
     * 获取树，数组方式
     * @param int $state
     */
    public function getArray($state = null) {
        $where = array();
        if ($state !== null) {
            $where[$this->_state] = $state;
        }
        
        $rows = Db()->getAll(static::$table)->where($where)->order("{$this->_depth}, {$this->_parent_id}, {$this->_sort}, {$this->_id}")->result();

        if (!$rows) {
            return array();
        }

        $tree = array();

        foreach ($rows as $row) {
            if ($parent = &$this->recursiveArray($tree, $row[$this->_parent_id])) {
                $parent['children'][] = $row;
            } else {
                $tree['children'][] = $row;
            }
        }

        return $tree;
    }

    /**
     * 递归数组
     * @param array $tree
     * @param int $key
     * @param bool $recursive
     * @return ptr 数组的引用地址
     */
    protected function &recursiveArray(&$tree, $key, $recursive = true) {
        if (is_array($tree['children'])) {
            foreach ($tree['children'] as &$node) {
                if ($node[$this->_id] == $key) {
                    return $node;
                }
                if ($recursive && $node['children']) {
                    $node1 = &$this->recursiveArray($node, $key, $recursive);
                    if ($node1) {
                        return $node1;
                    }
                }
            }
        }
        $null = null;
        return $null;
    }

    /**
     * 获取树，并转换为标题带└─的数组
     * @return array
     */
    public function getTreeArray($state = null, $indent = '└─ ') {
        $tree = $this->getTree($state);
        $arraytree = array();
        $this->recursiveArrayTree($arraytree, $tree, $indent);
        return $arraytree;
    }

    /**
     * 将树转换为逐行输出数组
     * @param array $childs
     * @param Treenode $node
     */
    protected function recursiveArrayTree(&$arraytree, $node, $indent = '└─ ') {
        if ($node && $node->hasChild()) {
            $childs = $node->getChildNodes();
            foreach ($childs as $child) {
                $data = $child->getData();
                if ($indent) {
                    $repeat_str = $data[$this->_depth] ? str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;', $data[$this->_depth] - 1) . $indent : '';
                    $data[$this->_title] = $repeat_str . $data[$this->_title];
                }
                $arraytree[] = $data;
                $this->recursiveArrayTree($arraytree, $child, $indent);
            }
        }
    }

    /**
     * 递归为标题添加缩进
     * 
     * @param Treenode $tree
     * @param string $indent
     */
    protected function recursiveTreeIndent($tree, $indent = '└─ ') {
        if ($tree && $tree->hasChild()) {
            $childs = $tree->getChildNodes();
            foreach ($childs as $child) {
                if ($indent) {
                    $repeat_str = $child->getLevel() ? str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;', $child->getLevel() - 1) . $indent : '';
                    $child->setTitle($repeat_str . $child->getTitle());
                }
                $this->recursiveTreeIndent($child, $indent);
            }
        }
    }

    /**
     * 获取所有下线ID
     * @param int $id
     * @param int $offset 起始深度
     * @param type $length 深度
     * @return array
     */
    public function getTreeNodesIds($id, $offset = 0, $length = 0) {
        $depth_limit = "";
        if ($offset || $length) {
            $depth_limit = " and t2.{$this->_depth} between (t1.{$this->_depth} + 1 + {$offset}) and (t1.{$this->_depth} + 1 + {$offset} + {$length} - 1) ";
        }
        $sql = "select t2.{$this->_id} from " . Db()->tablename(static::$table) . " t1 "
                . "inner join " . Db()->tablename(static::$table) . " t2 on t2.{$this->_parents} like concat(t1.{$this->_parents}, t1.{$this->_id}, ',%') $depth_limit "
                . "where t1.{$this->_id} = {$id} order by t2.{$this->_depth}, t2.{$this->_id}";
        $result = Db()->fetchCol($sql);
        if (!$result) {
            return array();
        }
        return $result;
    }

    /**
     * 获取所有下线
     * @param type $id
     * @param int $offset 起始深度
     * @param int $length 深度
     * @return array
     */
    public function getTreeNodes($id, $offset = 0, $length = 0) {
        $depth_limit = "";
        if ($offset || $length) {
            $depth_limit = " and t2.{$this->_depth} between (t1.{$this->_depth} + 1 + {$offset}) and (t1.{$this->_depth} + 1 + {$offset} + {$length} - 1) ";
        }
        $sql = "select t2.* from " . Db()->tablename(static::$table) . " t1 "
                . "inner join " . Db()->tablename(static::$table) . " t2 on t2.{$this->_parents} like concat(t1.{$this->_parents}, t1.{$this->_id}, ',%') $depth_limit "
                . "where t1.{$this->_id} = {$id} order by t2.{$this->_depth}, t2.{$this->_id}";
        $result = Db()->fetchAll($sql);
        if (!$result) {
            return array();
        }
        return $result;
    }

    /**
     * 统计所有下线数量
     * @param int $id
     * @param int $offset 起始深度
     * @param int $length 深度
     * @return int
     */
    public function countTreeNodes($id, $offset = 0, $length = 0) {
        $depth_limit = "";
        if ($offset || $length) {
            $depth_limit = " and t2.{$this->_depth} between (t1.{$this->_depth} + 1 + {$offset}) and (t1.{$this->_depth} + 1 + {$offset} + {$length} - 1) ";
        }
        $sql = "select count(t2.{$this->_id}) from " . Db()->tablename(static::$table) . " t1 "
                . "inner join " . Db()->tablename(static::$table) . " t2 on t2.{$this->_parents} like concat(t1.{$this->_parents}, t1.{$this->_id}, ',%') $depth_limit "
                . "where t1.{$this->_id} = {$id} order by t2.{$this->_depth}, t2.{$this->_id}";
        return intval(Db()->fetchOne($sql));
    }

    /**
     * 获取直推下线ID
     */
    public function getChildNodesIds($id) {
        return Db()->getCol(static::$table)->where(array($this->_parent_id => $id))->fields($this->_id)->result();
    }

    /**
     * 获取直推下线
     */
    public function getChildNodes($id, $start = 0, $length = 0) {
        if ($length) {
            return Db()->getAll(static::$table)->where(array($this->_parent_id => $id))->order("{$this->_id} desc")->limit($start, $length)->result();
        } else {
            return Db()->getAll(static::$table)->where(array($this->_parent_id => $id))->order("{$this->_id} desc")->result();
        }
    }

    /**
     * 统计直推下线数量
     */
    public function countChildNodes($id) {
        return Db()->count(static::$table)->where(array($this->_parent_id => $id))->result();
    }

    /**
     * 获取指定层级的上线
     * @param int $id
     * @param int $offset
     * @param int $length
     * @return array
     */
    public function getReverseParentIds($id, $offset = 0, $length = 0) {
        $parents = parent::get($id, $this->_parents_reverse);

        if ($parents) {
            $parents = substr($parents, 0, strlen($parents) - 1);
            $parents = explode(',', $parents);
            if ($length) {
                $parents = array_slice($parents, $offset, $length);
            } else {
                if ($offset) {
                    $parents = array_slice($parents, $offset);
                }
            }
        } else {
            $parents = array();
        }

        return $parents;
    }

    public function getParentIds($id, $with_self = false) {
        $parents = parent::get($id, $this->_parents);
        $parents = explode(',', $parents);
        $count = count($parents);
        if (!$parents[$count - 1]) {
            array_splice($parents, $count - 1, 1);
        }
        if ($with_self) {
            $parents[] = $id;
        }
        return $parents;
    }

}
